/*
 * Copyright (C) 2012, EADS France
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */

package xowl.store.rdf;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import xowl.lang.owl2.AnonymousIndividual;
import xowl.store.cache.StringStore;

/**
 *
 * @author Laurent WOUTERS
 */
public class RDFGraph {
    private static final int initSize = 1024;
    
    public static interface Key { }
    
    private abstract static class Edge {
        private Key property;
        private Multiplicity counters;
        private Edge next;
        public Edge(xowl.lang.owl2.Ontology ontology, RDFProperty property) {
            this.property = property._getKey();
            this.counters = new Multiplicity(ontology);
            this.next = null;
        }
        protected abstract boolean equalsValue(RDFNode value);
        public static Edge create(xowl.lang.owl2.Ontology onto, RDFProperty property, RDFNode value) {
            switch (value.getNodeType()) {
                case IRIReference: return new EdgeToIRI(onto, property, value);
                case Anonymous: return new EdgeToAnon(onto, property, value);
                case Blank: return new EdgeToBlank(onto, property, value);
                case Literal: return new EdgeToLiteral(onto, property, value);
            }
            // Cannot handle dynamic nodes
            return null;
        }
        public void increment(xowl.lang.owl2.Ontology onto, RDFProperty property, RDFNode value) {
            Edge current = this;
            Edge parent = this;
            while (current != null) {
                if (current.property == property._getKey() && equalsValue(value)) {
                    current.counters.increment(onto);
                    return;
                }
                parent = current;
                current = current.next;
            }
            parent.next = Edge.create(onto, property, value);
        }
        public Edge decrement(xowl.lang.owl2.Ontology onto, RDFProperty property, RDFNode value) {
            Edge current = this;
            Edge parent = this;
            while (current != null) {
                if (current.property == property._getKey() && equalsValue(value)) {
                    current.counters = current.counters.decrement(onto);
                    if (current.counters == null) {
                        if (current == this)
                            return this.next;
                        parent.next = current.next;
                    }
                    return this;
                }
                parent = current;
                current = current.next;
            }
            return this;
        }
    }
    private static class EdgeToIRI extends Edge {
        private Key value;
        @Override protected boolean equalsValue(RDFNode value) { return this.value == value._getKey(); }
        public EdgeToIRI(xowl.lang.owl2.Ontology ontology, RDFProperty property, RDFNode value) {
            super(ontology, property);
            this.value = value._getKey();
        }
    }
    private static class EdgeToAnon extends Edge {
        private AnonymousIndividual value;
        @Override protected boolean equalsValue(RDFNode value) { return this.value == value._getAnon(); }
        public EdgeToAnon(xowl.lang.owl2.Ontology ontology, RDFProperty property, RDFNode value) {
            super(ontology, property);
            this.value = value._getAnon();
        }
    }
    private static class EdgeToBlank extends Edge {
        private int value;
        @Override protected boolean equalsValue(RDFNode value) { return this.value == value._getBlankID(); }
        public EdgeToBlank(xowl.lang.owl2.Ontology ontology, RDFProperty property, RDFNode value) {
            super(ontology, property);
            this.value = value._getBlankID();
        }
    }
    private static class EdgeToLiteral extends Edge {
        private Literal value;
        @Override protected boolean equalsValue(RDFNode value) { return this.value == value._getLiteral(); }
        public EdgeToLiteral(xowl.lang.owl2.Ontology ontology, RDFProperty property, RDFNode value) {
            super(ontology, property);
            this.value = value._getLiteral();
        }
    }
    
    private static class Multiplicity {
        private xowl.lang.owl2.Ontology ontology;
        private int count;
        private Multiplicity next;
        public Multiplicity(xowl.lang.owl2.Ontology onto) {
            this.ontology = onto;
            this.count = 1;
            this.next = null;
        }
        public void increment(xowl.lang.owl2.Ontology onto) {
            Multiplicity current = this;
            Multiplicity parent = this;
            while (current != null) {
                if (current.ontology == onto) {
                    count++;
                    return;
                }
                parent = current;
                current = current.next;
            }
            parent.next = new Multiplicity(onto);
        }
        public Multiplicity decrement(xowl.lang.owl2.Ontology onto) {
            Multiplicity current = this;
            Multiplicity parent = this;
            while (current != null) {
                if (current.ontology == onto) {
                    count--;
                    if (count == 0) {
                        if (current == this)
                            return this.next;
                        parent.next = current.next;
                    }
                    return this;
                }
                parent = current;
                current = current.next;
            }
            return this;
        }
    }
    
    public static class Literal {
        private Key type;
        private Literal next;
        public Literal(Key type) { this.type = type; }
    }
    
    private StringStore sStore;
    private Map<Key, Literal> mapLiterals;
    private Map<Key, Edge> edgesIRI;
    private Map<xowl.lang.owl2.AnonymousIndividual, Edge> edgesAnon;
    private Edge[] edgesBlank;
    private int nextBlank;
    
    public RDFGraph() {
        sStore = new xowl.store.cache.StringStore();
        mapLiterals = new HashMap<Key, Literal>();
        edgesIRI = new HashMap<Key, Edge>();
        edgesAnon = new HashMap<xowl.lang.owl2.AnonymousIndividual, Edge>();
        edgesBlank = new Edge[initSize];
        nextBlank = 1;
    }
    
    public RDFIRIReference getNodeIRI(String iri) {
        Key key = sStore.store(iri);
        xowl.lang.owl2.IRI oIri = new xowl.lang.owl2.IRI();
        oIri.setHasValue(iri);
        RDFIRIReference node = new RDFIRIReference(key, oIri);
        return node;
    }
    
    public RDFAnonymousNode getAnonymousNode(AnonymousIndividual anon) {
        return new RDFAnonymousNode(anon);
    }
    
    public RDFBlankNode getBlankNode() {
        return new RDFBlankNode(nextBlank++);
    }
    
    public RDFLiteralNode getNodeLiteral(String lex, String datatype) {
        Key lexKey = sStore.store(lex);
        Key typeKey = sStore.store(datatype);
        Literal nl = mapLiterals.get(lexKey);
        if (nl != null) {
            nl = lookupLiteral(nl, typeKey);
        } else {
            nl = new Literal(typeKey);
            mapLiterals.put(lexKey, nl);
        }
        xowl.lang.owl2.IRI iri = new xowl.lang.owl2.IRI();
        iri.setHasValue(datatype);
        xowl.lang.owl2.Literal literal = new xowl.lang.owl2.Literal();
        literal.setLexicalValue(lex);
        literal.setMemberOf(iri);
        return new RDFLiteralNode(nl, literal);
    }
    
    private Literal lookupLiteral(Literal root, Key type) {
        Literal current = root;
        Literal parent = root;
        while (current != null) {
            if (current.type == type)
                return current;
            parent = current;
            current = current.next;
        }
        parent.next = new Literal(type);
        return parent.next;
    }
    
    public void add(xowl.lang.owl2.Ontology ontology, RDFSubjectNode subject, RDFProperty property, RDFNode value) {
        switch (subject.getNodeType()) {
            case IRIReference: add_IRI(ontology, subject, property, value); break;
            case Anonymous: add_Anon(ontology, subject, property, value);  break;
            case Blank: add_Blank(ontology, subject, property, value); break;
        }
    }
    private void add_IRI(xowl.lang.owl2.Ontology ontology, RDFSubjectNode subject, RDFProperty property, RDFNode value) {
        Key key = subject._getKey();
        Edge edge = edgesIRI.get(key);
        if (edge != null)
            edge.increment(ontology, property, value);
        else
            edgesIRI.put(key, Edge.create(ontology, property, value));
    }
    private void add_Anon(xowl.lang.owl2.Ontology ontology, RDFSubjectNode subject, RDFProperty property, RDFNode value) {
        xowl.lang.owl2.AnonymousIndividual anon = subject._getAnon();
        Edge edge = edgesAnon.get(anon);
        if (edge != null)
            edge.increment(ontology, property, value);
        else
            edgesAnon.put(anon, Edge.create(ontology, property, value));
    }
    private void add_Blank(xowl.lang.owl2.Ontology ontology, RDFSubjectNode subject, RDFProperty property, RDFNode value) {
        int index = subject._getBlankID();
        while (index > edgesBlank.length)
            edgesBlank = Arrays.copyOf(edgesBlank, edgesBlank.length + initSize);
        Edge edge = edgesBlank[index];
        if (edge != null)
            edge.increment(ontology, property, value);
        else
            edgesBlank[index] = Edge.create(ontology, property, value);
    }
    
    public void remove(xowl.lang.owl2.Ontology ontology, RDFSubjectNode subject, RDFProperty property, RDFNode value) {
        switch (subject.getNodeType()) {
            case IRIReference: remove_IRI(ontology, subject, property, value); break;
            case Anonymous: remove_Anon(ontology, subject, property, value);  break;
            case Blank: remove_Blank(ontology, subject, property, value); break;
        }
    }
    private void remove_IRI(xowl.lang.owl2.Ontology ontology, RDFSubjectNode subject, RDFProperty property, RDFNode value) {
        Key key = subject._getKey();
        Edge edge = edgesIRI.get(key);
        if (edge == null)
            return;
        Edge temp = edge.decrement(ontology, property, value);
        if (temp == null)
            edgesIRI.remove(key);
        else if (temp != edge)
            edgesIRI.put(key, temp);
    }
    private void remove_Anon(xowl.lang.owl2.Ontology ontology, RDFSubjectNode subject, RDFProperty property, RDFNode value) {
        xowl.lang.owl2.AnonymousIndividual anon = subject._getAnon();
        Edge edge = edgesAnon.get(anon);
        if (edge == null)
            return;
        Edge temp = edge.decrement(ontology, property, value);
        if (temp == null)
            edgesAnon.remove(anon);
        else if (temp != edge)
            edgesAnon.put(anon, temp);
    }
    private void remove_Blank(xowl.lang.owl2.Ontology ontology, RDFSubjectNode subject, RDFProperty property, RDFNode value) {
        int index = subject._getBlankID();
        if (index >= edgesBlank.length)
            return;
        Edge edge = edgesBlank[index];
        if (edge == null)
            return;
        edgesBlank[index] = edge.decrement(ontology, property, value);
    }
}
